package com.yami.shop.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.bean.app.param.PersonalAccountOpenParam;
import com.yami.shop.bean.app.param.UserRegisterParam;
import com.yami.shop.bean.app.param.XsQueryParam;
import com.yami.shop.bean.event.DelRongEvent;
import com.yami.shop.bean.event.UserDataEvent;
import com.yami.shop.bean.event.UserDestroyEvent;
import com.yami.shop.bean.model.*;
import com.yami.shop.bean.param.*;
import com.yami.shop.bean.param.hnaPay.BaseTransForm;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.enums.UserLevelType;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.i18n.LanguageEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.*;
import com.yami.shop.config.HnaPayConfig;
import com.yami.shop.config.ShopConfig;
import com.yami.shop.dao.UserMapper;
import com.yami.shop.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author lgh on 2018/09/11.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Value("${yami.expose.operation.auth:}")
    private Boolean permission;

    private final UserMapper userMapper;
    private final UserExtensionService userExtensionService;
    private final UserCollectionService userCollectionService;
    private final UserCollectionShopService userCollectionShopService;
    private final ApplicationEventPublisher eventPublisher;
    private final UserSecurityConfigService userSecurityConfigService;
    private final HnaPayConfig hnaPayConfig;
    private final SysConfigService sysConfigService;
    private final AttachFileService attachFileService;
    private final ShopConfig shopConfig;
    @Override
    public User getUserByUserId(String userId) {
        return userMapper.selectById(userId);
    }

    @Override
    public void setUserLevelBylevelId(Integer level, Integer levelType) {
        userMapper.setUserLevelBylevelId(level, levelType);
        userExtensionService.setUserLevelBylevelId(level, levelType);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setUserLevelByGrowth(Integer level, Integer minNeedGrowth, Integer maxNeedGrowth, Integer levelType) {
        userMapper.setMaxUserLevelBylevelId(level, minNeedGrowth, maxNeedGrowth, levelType);
//        userExtensionService.setMaxUserLevelByLevelId(level, minNeedGrowth, maxNeedGrowth, levelType);
    }


    /**
     * 看看有没有校验验证码成功的标识
     *
     * @param userRegisterParam
     * @param checkRegisterSmsFlag
     */
    @Override
    public ServerResponseEntity<Void> validate(UserRegisterParam userRegisterParam, String checkRegisterSmsFlag) {
        if (StrUtil.isBlank(userRegisterParam.getCheckRegisterSmsFlag())) {
            // 验证码已过期，请重新发送验证码校验
            return ServerResponseEntity.showFailMsg(I18nMessage.getMessage("yami.verification.expire"));
        } else {
            String checkRegisterSmsFlagMobile = RedisUtil.get(checkRegisterSmsFlag);
            if (!Objects.equals(checkRegisterSmsFlagMobile, userRegisterParam.getMobile())) {
                // 验证码已过期，请重新发送验证码校验
                return ServerResponseEntity.showFailMsg(I18nMessage.getMessage("yami.verification.expire"));
            }
        }
        return ServerResponseEntity.success();
    }

    @Override
    public User getUserDetail(String userId) {
        return userMapper.getUserDetail(userId);
    }

    @Override
    public IPage<User> getUserPage(PageParam<User> page, User user) {
        return userMapper.getUserPage(page, user);
    }

    @Override
    public Map<String, User> getUserListByUserNumbers(Set<String> keySet) {
        if (keySet.size() == 0) {
            return new HashMap<>(16);
        }
        List<User> userList = userMapper.getUserListByUserNumbers(keySet);
        Map<String, User> userMap = userList.stream().collect(Collectors.toMap(User::getUserId, user -> user));
        return userMap;
    }

    @Override
    public CustomerRespParam countUserByParam(CustomerReqParam param) {
        CustomerRespParam respParam = new CustomerRespParam();
        Date endTime = param.getEndTime();
        Date startTime = param.getStartTime();
        // 累积粉丝数
        param.setAppId(1);
        param.setDateTime(endTime);
        if (Objects.isNull(param.getShopId())) {
            param.setMember(1);
            // 累计应该没有时间
            param.setStartTime(null);
            param.setEndTime(null);
            param.setDateTime(null);
            respParam.setFashNum(userMapper.countUserByParam(param));
            param.setDateTime(startTime);
            respParam.setPreFashNum(userMapper.countUserByParam(param));
            respParam.setFashNumRate(divRate(respParam.getPreFashNum(), respParam.getFashNum(), 4));
        }

        // 累积会员数
        param.setDateTime(null);
        param.setMember(null);
        param.setAppId(null);
        param.setDateTime(endTime);
        respParam.setMember(userMapper.countMemberByParam(param));
        param.setDateTime(startTime);
        respParam.setPreMember(userMapper.countMemberByParam(param));
        respParam.setMemberRate(divRate(respParam.getMember() - respParam.getPreMember(), respParam.getMember(), 4));

        return respParam;
    }

    @Override
    public IPage<UserManagerParam> getUserInfoPage(PageParam<User> page, UserManagerReqParam user, Integer lang) {
        // user基本信息 积分 当前余额  累计金额
        Page<UserManagerParam> pages = new Page<>();
        pages.setCurrent(page.getCurrent());
        pages.setSize(page.getSize());
        List<UserManagerParam> list = userMapper.getUserPageByParam(new PageAdapter(page), user);
        for (UserManagerParam userManagerParam : list) {
            // 订单实际支付金额/订单没有优惠的金额  不包含积分订单
            userManagerParam.setAverDiscount(divAverage(userManagerParam.getOrderActualAmount(), userManagerParam.getConsAmount(), 2));
            if (BooleanUtil.isFalse(permission)) {
                if (Objects.nonNull(userManagerParam.getUserMobile())) {
                    userManagerParam.setUserMobile(PhoneUtil.hideBetween(userManagerParam.getUserMobile()).toString());
                }
                // 昵称符合正则时就**
                if (PrincipalUtil.isMobile(userManagerParam.getNickName())) {
                    userManagerParam.setNickName(PhoneUtil.hideBetween(userManagerParam.getNickName()).toString());
                }
            }
            handleUserNullValue(userManagerParam);
        }
        // 等级国际化
        List<Long> levelIdList = list.stream().map(UserManagerParam::getLevelId).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(levelIdList)) {
            Map<Long, List<UserLevelLang>> levelLangMap = userMapper.getUserLevelById(levelIdList).stream().collect(Collectors.groupingBy(UserLevelLang::getLevelId));
            for (UserManagerParam userManagerParam : list) {
                userManagerParam.setUserLevelLangList(levelLangMap.get(userManagerParam.getLevelId()));
            }
        }
        pages.setRecords(list);
        Integer total = userMapper.countGetUserPageByParam(new PageAdapter(page), user);
        pages.setTotal(total);
        return pages;
    }

    /**
     * 会员等级国际化
     *
     * @param list
     */
    private void userLevelLang(List<UserManagerParam> list) {
        // 用户等级国际化处理
        List<Long> levelIdList = list.stream().map(UserManagerParam::getLevelId).collect(Collectors.toList());
        if (CollUtil.isEmpty(levelIdList)) {
            return;
        }
        Map<Long, List<UserLevelLang>> levelLangMap = userMapper.getUserLevelById(levelIdList).stream().collect(Collectors.groupingBy(UserLevelLang::getLevelId));
        for (UserManagerParam userManagerParam : list) {
            List<UserLevelLang> userLevelLangs = levelLangMap.get(userManagerParam.getLevelId());
            if (Objects.isNull(userLevelLangs) && userLevelLangs.size() == 0) {
                continue;
            }
            for (UserLevelLang userLevelLang : userLevelLangs) {
                if (Objects.equals(userLevelLang.getLang(), I18nMessage.getLang())) {
                    userManagerParam.setLevelName(userLevelLang.getLevelName());
                }
            }
        }
    }

    @Override
    public IPage<UserManagerParam> getUserInfoPage(Page<User> page, UserManagerReqParam user) {
        // user基本信息 积分 当前余额  累计金额
        Page<UserManagerParam> pages = new Page<>();
        pages.setSize(page.getSize());
        pages.setCurrent(page.getCurrent());
        List<UserManagerParam> list = userMapper.getUserPageByParam(new PageAdapter(page), user);
        for (UserManagerParam userManagerParam : list) {
            handleUserNullValue(userManagerParam);
        }
        userLevelLang(list);
        pages.setRecords(list);
        return pages;
    }

    private void handleUserNullValue(UserManagerParam userManagerParam) {
        if (Objects.isNull(userManagerParam.getConsAmount())) {
            userManagerParam.setConsAmount(Constant.ZERO_DOUBLE);
        }
        if (Objects.isNull(userManagerParam.getConsTimes())) {
            userManagerParam.setConsTimes(0);
        }
        if (Objects.isNull(userManagerParam.getReduceAmount())) {
            userManagerParam.setReduceAmount(Constant.ZERO_DOUBLE);
        }
        userManagerParam.setAverDiscount(Arith.div(userManagerParam.getOrderActualAmount(), userManagerParam.getConsAmount(), 2));
        if (Objects.isNull(userManagerParam.getAfterSaleAmount())) {
            userManagerParam.setAfterSaleAmount(Constant.ZERO_DOUBLE);
        }
        if (Objects.isNull(userManagerParam.getAfterSaleTimes())) {
            userManagerParam.setAfterSaleTimes(0);
        }
        if (Objects.isNull(userManagerParam.getSumScore())) {
            userManagerParam.setSumScore(0);
        }
        if (Objects.isNull(userManagerParam.getRechargeAmount())) {
            userManagerParam.setRechargeAmount(Constant.ZERO_DOUBLE);
        }
        if (Objects.isNull(userManagerParam.getRechargeTimes())) {
            userManagerParam.setRechargeTimes(0);
        }
    }

    @Override
    public UserManagerParam getuserInfo(String userId) {
        UserManagerParam param = userMapper.getuserInfo(userId);
        if (BooleanUtil.isFalse(permission) && PrincipalUtil.isMobile(param.getNickName())) {
            param.setNickName(PhoneUtil.hideBetween(param.getNickName()).toString());
        }
        param.setCurrentScore(param.getScore());
        eventPublisher.publishEvent(new UserDataEvent(param));
        return param;
    }

    @Override
    public void downloadUserModel(HttpServletResponse response) {
        // 下载用户导入模板
        downloadUserModelExcel(response);
    }

    @Override
    public UserExcelParam parseUserImportFile(MultipartFile excelFile, List<UserLevelParam> userLevels) throws IOException {
        Integer lang = I18nMessage.getDbLang();
        UserExcelParam resParam = new UserExcelParam();
        resParam.setSuccess(false);
        resParam.setImmediately(false);
        // 普通会员
        List<UserLevelParam> ordinary = userLevels.stream().filter(item -> Objects.equals(0, item.getLevelType())).collect(Collectors.toList());
        // 付费会员
        List<UserLevelParam> pay = userLevels.stream().filter(item -> Objects.equals(1, item.getLevelType())).collect(Collectors.toList());
        try (InputStream inputStream = excelFile.getInputStream(); Workbook workBook = WorkbookFactory.create(inputStream)) {
            // * 2.1不是Excel文件，直接返回提示信息
            if (!(excelFile.getOriginalFilename().contains(Constant.XLS_FILE) || excelFile.getOriginalFilename().contains(Constant.XLSX_FILE))) {
                // 格式错误
                throw new YamiShopBindException("yami.file.error");
            }
            // 读取工作表
            Sheet sheet = workBook.getSheetAt(0);
            int rowNums = sheet.getPhysicalNumberOfRows();
            // 校验是否填写内容
            if (rowNums <= 1) {
                // 文件无内容
                throw new YamiShopBindException("yami.document.no.content");
            }
            // 总计导入数据条数
            int total = 0;
            List<UserRegisterExcelParam> userList = new ArrayList<>();
            Set<String> phoneSet = new HashSet<>();
            // Set<String> mailSet = new HashSet<>();
            List<String> errorRows = new ArrayList<>();
            long currentTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
            // 循环读取每一行数据并校验
            for (int i = 1; i < rowNums; i++) {
                total++;
                try {
                    // 读取行
                    Row row = sheet.getRow(i);
                    int cell = 0;
                    // 序号
                    String serialNo = PoiExcelUtil.getRowValue(row, cell, false);
                    // 手机号
                    String phone = PoiExcelUtil.getRowValue(row, ++cell, false);
                    // 密码
                    String password = PoiExcelUtil.getRowValue(row, ++cell, false);
                    // 会员类型(0普通会员/1付费会员)，不填默认为普通会员
                    String levelType = PoiExcelUtil.getRowValue(row, ++cell, false);
                    // vip结束时间
                    String vipEndTime = PoiExcelUtil.getRowValue(row, ++cell, false);
                    if (Objects.equals(levelType, UserLevelType.VIP.value().toString())) {
                        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                        LocalDateTime ldt = LocalDateTime.parse(DateUtil.parse(vipEndTime, "yyyy-MM-dd").toString(), dtf);
                        // 获取毫秒数
                        long milliSecond = ldt.toInstant(ZoneOffset.of("+8")).toEpochMilli();
                        if (currentTime >= milliSecond) {
                            levelType = UserLevelType.ORDINARY.value().toString();
                        }
                    }
                    // 用户昵称
                    String nickName = PoiExcelUtil.getRowValue(row, ++cell, false);
                    // 邮箱(已弃用)
                    // String userMail = PoiExcelUtil.getRowValue(row, ++cell, false);
                    // 性别
                    String sex = PoiExcelUtil.getRowValue(row, ++cell, false);
                    if(StringUtils.isNotBlank(sex) &&!"0".equals(sex)&&!"1".equals(sex)){
                        throw new YamiShopBindException("性别格式错误");
                    }
                    // 生日
                    String birthDate = PoiExcelUtil.getRowValue(row, ++cell, false);
                    // 备注
                    String userMemo = PoiExcelUtil.getRowValue(row, ++cell, false);
                    // 积分
                    String score = PoiExcelUtil.getRowValue(row, ++cell, true);
                    // 余额
                    String balance = PoiExcelUtil.getRowValue(row, ++cell, true);
                    // 成长值
                    String growth = PoiExcelUtil.getRowValue(row, ++cell, true);
                    // if (checkData(lang, phoneSet, mailSet, errorRows, i, phone, password, nickName, userMail)){ continue; }
                    phoneSet.add(phone);
                    // mailSet.add(userMail);
                    UserRegisterExcelParam param = getUserRegisterExcelParam(lang, ordinary, pay, errorRows, i, serialNo, phone, password, levelType, vipEndTime, nickName, sex, birthDate, userMemo, score, balance, growth);
                    if (param == null) {
                        continue;
                    }
                    userList.add(param);
                } catch (Exception e) {
                    errorRows.add(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行" + e.getMessage() : "Line " + (i + 1) + e.getMessage());
                    // 错误信息提示
                    resParam.setRow(i + 1);
                    resParam.setMsg(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行数据有错误:" + e.getMessage() : "Line " + (i + 1) + "error: " + e.getMessage());
                    resParam.setImmediately(true);
                    return resParam;
                }
            }
            if (CollectionUtils.isNotEmpty(userList)) {
                setResParam(resParam, total, userList, errorRows);
                return resParam;
            }
            resParam.setMsg(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "数据错误!导入0条" : "Data error! Import 0 items");
            resParam.setImmediately(true);
            return resParam;
        } catch (Exception e) {
            resParam.setMsg(e.getMessage());
            resParam.setImmediately(true);
            return resParam;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void destroyUser(String userId) {
        // 清空注销用户的所有数据
        userSecurityConfigService.remove(new LambdaQueryWrapper<UserSecurityConfig>().eq(UserSecurityConfig::getUserId, userId));
        userExtensionService.remove(new LambdaQueryWrapper<UserExtension>().eq(UserExtension::getUserId, userId));
        userCollectionService.remove(new LambdaQueryWrapper<UserCollection>().eq(UserCollection::getUserId, userId));
        userCollectionShopService.remove(new LambdaQueryWrapper<UserCollectionShop>().eq(UserCollectionShop::getUserId, userId));
        eventPublisher.publishEvent(new UserDestroyEvent(userId));
        // 删除融云信息
        eventPublisher.publishEvent(new DelRongEvent(userId));
        User user = new User();
        user.setStatus(0);
        user.setUserId(userId);
        userMapper.updateById(user);
    }

    private boolean checkData(Integer lang, Set<String> phoneSet, List<String> errorRows, int i, String phone, String password, String nickName, String userMail) {
        // 手机号必须是11位
        if (!PrincipalUtil.isMobile(phone)) {
            errorRows.add(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行手机号格式有误" : "The format of the cell phone number in line " + (i + 1) + " is wrong");
            return true;
        }
        if (StrUtil.isBlank(password)) {
            errorRows.add(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行密码为空" : "Line " + (i + 1) + " password is empty");
            return true;
        }
        if (Objects.isNull(nickName) || StrUtil.isBlank(nickName)) {
            errorRows.add(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行昵称为空" : "Line " + (i + 1) + " nickname is empty");
            return true;
        }
        if (nickName.length() > Constant.MAX_NICK_NAME_LENGTH) {
            errorRows.add(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行昵称长度超过15" : "Line " + (i + 1) + " nickname length exceeds 15");
            return true;
        }
        if (StrUtil.containsBlank(nickName)) {
            errorRows.add(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行昵称包含空格" : "The " + (i + 1) + " line of the nickname includes spaces");
            return true;
        }
        if (StrUtil.isNotBlank(phone) && phoneSet.contains(phone)) {
            errorRows.add(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行手机号码重复" : "Duplicate cell phone number in line " + (i + 1));
            return true;
        }
        return false;
    }

    private void setResParam(UserExcelParam resParam, int total, List<UserRegisterExcelParam> userList, List<String> errorRows) {
        resParam.setTotal(total);
        resParam.setSuccessNum(userList.size());
        resParam.setErrorNum(errorRows.size());
        resParam.setErrorRowInfos(errorRows);
        resParam.setParam(Json.toJsonString(userList));
        resParam.setImmediately(false);
        resParam.setSuccess(true);
    }

    @NotNull
    private UserRegisterExcelParam getUserRegisterExcelParam(Integer lang, List<UserLevelParam> ordinary, List<UserLevelParam> pay, List<String> errorRows, int i, String serialNo, String phone, String password, String levelType, String vipEndTime, String nickName, String sex, String birthDate, String userMemo, String score, String balance, String growth) {
        UserRegisterExcelParam param = new UserRegisterExcelParam();
        param.setSerialNo(serialNo);
        param.setPhone(phone);
        param.setNickName(nickName);
        // param.setUserMail(userMail);
        param.setPassword(password);
        param.setSex(sex);
        param.setBirthDate(birthDate);
        param.setUserMemo(userMemo);
        String error = null;
        try {
            error = score;
            long scoreValue = StrUtil.isBlank(score) ? 0 : new BigDecimal(score).longValue();
            param.setScore(Math.max(scoreValue, 0));
            error = balance;
            double balanceValue = StrUtil.isBlank(balance) ? 0.00 : Arith.roundByBanker(new BigDecimal(balance).doubleValue(), 2);
            param.setBalance(balanceValue < 0 ? 0.00 : balanceValue);
            error = growth;
            int growthValue = StrUtil.isBlank(growth) ? 0 : new BigDecimal(growth).intValue();
            param.setGrowth(Math.max(growthValue, 0));
            error = levelType;
            param.setLevelType(StrUtil.isBlank(levelType) ? 0 : new BigDecimal(levelType).intValue());
        } catch (NumberFormatException e) {
            throw new YamiShopBindException(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "输入内容[" + error + "]格式错误" : "Wrong format of input" + error);
        }
        if (Objects.equals(1, param.getLevelType()) && StrUtil.isBlank(vipEndTime)) {
            errorRows.add(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? "第" + (i + 1) + "行" + "付费会员的vip结束时间为空" : "Line" + (i + 1) + "is empty for vip end time for paid members");
            return null;
        }
        if (Objects.equals(1, param.getLevelType())) {
            param.setVipEndTime(DateUtil.parse(vipEndTime, "yyyy-MM-dd"));
        }
        param.setLevel(getUserLevelByGrowthAndType(Objects.equals(0, param.getLevelType()) ? ordinary : pay, param.getGrowth()));
        return param;
    }

    private Integer getUserLevelByGrowthAndType(List<UserLevelParam> levelParams, Integer growth) {
        int level = 1;
        for (UserLevelParam levelParam : levelParams) {
            if (growth >= levelParam.getNeedGrowth()) {
                level = levelParam.getLevel();
            }
        }
        return level;
    }

    private void downloadUserModelExcel(HttpServletResponse response) {
        // 通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getBigWriter();
        // 商品导出or模板
        List<String> headerList = getHeadList();
        Sheet sheet = writer.getSheet();
        writer.writeRow(headerList);

        // 列表设置为文本格式
        CellStyle style = writer.createCellStyle();
        try (Workbook workbook = writer.getWorkbook()) {
            DataFormat format = workbook.createDataFormat();
            style.setDataFormat(format.getFormat("@"));

            for (int i = 0; i < headerList.size(); i++) {
                if (i == 0) {
                    sheet.setColumnWidth(i, 10 * 256);
                    sheet.setDefaultColumnStyle(i, style);
                } else if (i == 3) {
                    sheet.setColumnWidth(i, 30 * 256);
                    style.setWrapText(true);
                    sheet.setDefaultColumnStyle(i, style);
                } else if (i == 4) {
                    sheet.setColumnWidth(i, 40 * 256);
                    style.setWrapText(true);
                    sheet.setDefaultColumnStyle(i, style);
                } else if (i == 9) {
                    sheet.setColumnWidth(i, 40 * 256);
                    sheet.setDefaultColumnStyle(i, style);
                } else {
                    sheet.setColumnWidth(i, 20 * 256);
                    sheet.setDefaultColumnStyle(i, style);
                }
            }
            // 会员类型 0普通会员 1付费会员
            String[] type = {"0", "1"};
            PoiExcelUtil.createDropDownList(sheet, type, 1, 50000, 3, 3);
            // 性别选项
            String[] sexList = {"0", "1"};
            PoiExcelUtil.createDropDownList(sheet, sexList, 1, 50000, 6, 6);
            PoiExcelUtil.writeExcel(response, writer);
        } catch (Exception e) {
            log.error("下载会员导入模板错误:", e);
        }
    }


    private Double divRate(Integer a, Integer b, Integer scale) {
        if (Objects.isNull(b) || b == 0 || Objects.isNull(a)) {
            return 0.0;
        }
        return Arith.div(a, b, scale);
    }

    private Double divAverage(Double a, Double b, Integer scale) {
        if (Objects.isNull(b) || b == 0 || Objects.isNull(a)) {
            return 0.0;
        }
        return Arith.div(a, b, scale);
    }

    private List<String> getHeadList() {
        Integer lang = I18nMessage.getDbLang();
        String[] header = {"序号", "手机号(必填)", "密码(必填)", "会员类型(0普通会员/1付费会员,必填)", "vip结束时间(格式:2021-11-01，会员类型是1则必填)", "用户昵称(必填，最多15个字)", "性别(0男/1女)", "生日(格式:1989-08-08)", "备注", "积分(限整数)", "余额(精确小数点后两位，四舍五入)", "成长值(限整数)"};
        String[] headerEn = {"Seq", "Mobile(*)", "Password(*)", "Member Type(0 General Member/1 Paid Member,*)", "Vip End Time(Format:2021-11-01，Required if membership type is 1)", "NickName(Maximum 15 characters*)", "Sex(0 Man/1 Female)", "BirthDay(Format:1989-08-08)", "Remarks", "Score(Limited to integers)", "Balance(Exactly two decimal places, rounded to the nearest two)", "Growth(Limited to integers)"};
        return Arrays.asList(Objects.equals(lang, LanguageEnum.LANGUAGE_ZH_CN.getLang()) ? header : headerEn);
    }

    @Override
    public List<User> getUserByUserIds(List<String> ids) {
        return userMapper.getUserByUserIds(ids);
    }

    @Override
    public IPage<CustomerManagerParam> pageCustomerByMultiShop(PageParam<CustomerManagerParam> page, CustomerManagerParam reqParam) {
        // 将sql拆分，避免多表join，提升性能和阅读性
        IPage<CustomerManagerParam> customerPage = userMapper.pageCustomerByMultiShop(page, reqParam);
        if (customerPage.getRecords().isEmpty()) {
            return customerPage;
        }
        // 补充其他数据
        List<String> userIds = customerPage.getRecords().stream().map(CustomerManagerParam::getUserId).collect(Collectors.toList());
        List<CustomerManagerParam> tempParams = userMapper.listCustomerOtherDataByMultiShop(userIds, reqParam.getShopId());
        Map<String, CustomerManagerParam> customerParamMap = new HashMap<>();
        for (CustomerManagerParam param : tempParams) {
            customerParamMap.put(param.getUserId(), param);
        }
        for (CustomerManagerParam param : customerPage.getRecords()) {
            BeanUtil.copyProperties(customerParamMap.get(param.getUserId()), param, CopyOptions.create().setIgnoreNullValue(true));
            // 平均折扣
            param.setAverDiscount(divAverage(param.getActualAmount(), param.getConsAmount(), 2));
            if (BooleanUtil.isFalse(permission) && Objects.nonNull(param.getUserMobile())) {
                param.setUserMobile(PhoneUtil.hideBetween(param.getUserMobile()).toString());
            }
        }
        return customerPage;
    }

    @Override
    public IPage<UserManagerParam> pageUserByMultiShop(PageParam<UserManagerParam> page, UserManagerReqParam reqParam) {
        return null;
    }

    @Override
    public Map<String, Object> personalAccountOpening(PersonalAccountOpenParam personalAccountOpenParam) {
        BaseTransForm base = hnaPayConfig.getR010TransForm(personalAccountOpenParam);
        try {
            Map<String, Object> responseMap = hnaPayConfig.post(base);
            if (responseMap.get("resultCode").equals("0000")) {
                this.update(Wrappers.lambdaUpdate(User.class).set(User::getRealName, personalAccountOpenParam.getUserName()).set(User::getUserMobile, personalAccountOpenParam.getMobile()).set(User::getIdType, personalAccountOpenParam.getCertType()).set(User::getIdNumber, personalAccountOpenParam.getCertNo()).set(User::getXsUserId, responseMap.get("userId")).eq(User::getUserId, personalAccountOpenParam.getMerUserId()));
            }
            return responseMap;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Map<String, Object> personalAccountQuery(String xsUserId) {
        BaseTransForm base = hnaPayConfig.getQ001TransForm(xsUserId);
        try {
            return hnaPayConfig.post(base);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Map<String, Object> personalAccountDetailQuery(XsQueryParam xsQueryParam) {
        BaseTransForm base = hnaPayConfig.getQ004TransForm(xsQueryParam);
        try {
            return  hnaPayConfig.post(base);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public User getUserDetailByCallPhone(String callPhone) {
        return userMapper.getUserDetailByCallPhone(callPhone);
    }

    @Override
    public void removeHwjInfo(String userId) {
        userMapper.removeHwjInfo(userId);
    }

    @Override
    public String createUserQrcode(Boolean isSave, String userId, String pic) {

        String fileName = "";
        try {
            if (StringUtils.isBlank(pic)) {
                pic = "2022/12/123.png";
            }
            String minioConfig = sysConfigService.getValue("MINIO_OSS_CONFIG");
            if (StringUtils.isBlank(minioConfig)) {
                return fileName;
            }

            Map map = Json.parseObject(minioConfig, Map.class);
            String imageRoot = (String) map.get("endpoint");
            String imageFile = (String) map.get("bucketName");
            if (StringUtils.isBlank(imageRoot) || StringUtils.isBlank(imageFile)) {
                return fileName;
            }
            URL imageurl = new URL(spliceUrl(imageRoot, imageFile, pic));
            Image image = ImageIO.read(imageurl);

            QrConfig config = new QrConfig(300,300);
            config.setImg(image);
            // 获取微发行链接地址
            String url = sysConfigService.getValue("USER_CENTER_URL") + "?userId=" + userId;
            BufferedImage qrgenerate = QrCodeUtil.generate(url, config);
            //读文件
            ByteArrayOutputStream qrOutputStream = new ByteArrayOutputStream();
            ImageIO.write(qrgenerate,"jpg", qrOutputStream);

            MultipartFile ossMultipartFile = new OssMultipartFile("111.jpg", ".jpg", qrOutputStream.toByteArray());
            //保存图片
            fileName = attachFileService.uploadFile(ossMultipartFile.getBytes(), UUID.fastUUID()+".jpg");
            if (isSave) {
                // 保存链接信息
                UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("user_id", userId);
                updateWrapper.set("qrcode_addr", fileName);
                this.update(updateWrapper);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fileName;
    }

    @Override
    public Boolean isBigMan(String cardNo) {
        int ages = IdcardUtil.getAgeByIdCard(cardNo);
        String sysConfigObject = shopConfig.getBigMan();
        if(StrUtil.isBlank(sysConfigObject)){
            return true;
        }
        String[] split = sysConfigObject.split(",");
        //获取用户年龄
        if (split.length>0 && ages < Integer.valueOf(split[0])) {
            //判断用户年龄是否满足18周岁
            //"根据国家相关法律法规未成年禁止注册！"
            return false;
        }
        if(split.length>1 && ages > Integer.valueOf(split[1])){
            //"注册年龄不符合交易所规定范围！";
            return false;
        }
        return true;
    }
    /**
     *  处理链接
     *
     * @param imageRoot
     * @param imageFile
     * @param pic
     * @return
     */
    private String spliceUrl(String imageRoot, String imageFile, String pic) {
        StringBuffer url = new StringBuffer("");
        url.append(imageRoot.replaceFirst("/$",""))
                .append("/").append(imageFile.replaceFirst("^/","").replaceFirst("/$",""))
                .append("/").append(pic.replaceFirst("^/",""));
        return url.toString();
    }
}
